#include "ModuleMessageHandler.h"
#include <QXmlQuery>
#include <QXmlResultItems>


ModuleMessageHandler::ModuleMessageHandler(QObject *parent)
    : QObject(parent)
{
    configFileName = "../conf/modules_conf.xml";
}

ModuleMessageHandler::~ModuleMessageHandler()
{
    while(!modules.isEmpty()) {
        delete modules.takeFirst();
    }
}

ZModuleSettings ModuleMessageHandler::findConfiguration(const QString &moduleName)
{
    ZModuleSettingsDOM dom;
    if(!dom.load(configFileName)) {
        qDebug() << "Failed to get settings for module " << moduleName;
        qDebug() << dom.getLastError();
        return ZModuleSettings();
    }
    return dom.get(moduleName);
}

bool ModuleMessageHandler::registerConfiguration(const ZModuleSettings& settings)
{
    ZModuleSettingsDOM dom;
    if(!dom.load(configFileName)) {
        qDebug() << "Failed to register configuration for module \""+settings.getName()+"\"";
        qDebug() << dom.getLastError();
        return false;
    }
    if(!dom.set(settings.getName(), settings)) {
        qDebug() << "Failed to register configuration for module \""+settings.getName()+"\"";
        qDebug() << dom.getLastError();
        return false;
    }
    if(!dom.save(configFileName)) {
        qDebug() << "Failed to register configuration for module \""+settings.getName()+"\"";
        qDebug() << dom.getLastError();
        return false;
    }
    return true;
}

void ModuleMessageHandler::sendConfigurationToModule(const ZModuleSettings& settings)
{
    const QString &moduleName = settings.getName();
    QStringList config = settings.getDevices();
    qDebug() << "[zigbus] sending configuration to module " << moduleName;
    foreach(QString device, config) {
        qDebug() << "[zigbus] \t" << device;
        QString function = xPL4Qt::function_name(device);
        QStringList args = xPL4Qt::function_arg_list(device);
        if(function == "numeric_output") {
            if(args.count() != 2) {
                qDebug() << "ERROR: bad syntax in " << device;
                continue;
            }
            ZigbusMessage configMessage = create_config_numeric_output(moduleName, args[0], (args[1] == "enable"));
            if(configMessage.isValid())
                emit zigbusMessageToSend(configMessage);
        }
        else if(function == "pwm") {
            if(args.count() != 2) {
                qDebug() << "ERROR: bad syntax in " << device;
                continue;
            }
            ZigbusMessage configMessage = create_config_pwm(moduleName, args[0], args[1].toInt());
            if(configMessage.isValid())
                emit zigbusMessageToSend(configMessage);
        }
        else if(function == "heater") {
            if(args.count() != 2) {
                qDebug() << "ERROR: bad syntax in " << device;
                continue;
            }
            ZigbusMessage configMessage = create_config_heater(moduleName, args[0], args[1]);
            if(configMessage.isValid())
                emit zigbusMessageToSend(configMessage);
        }
        else if(function == "meteo") {
            if(args.count() != 2) {
                qDebug() << "ERROR: bad syntax in " << device;
                continue;
            }
            ZigbusMessage configMessage = create_config_meteo(moduleName, args[0], args[1]);
            if(configMessage.isValid())
                emit zigbusMessageToSend(configMessage);
        }
    }
}

ZigbusMessage ModuleMessageHandler::create_config_numeric_output(const QString &moduleName, const QString &pinId, bool enable)
{
    bool conversionOK = false;
    int inetid = moduleName.left(2).toInt(&conversionOK);
    if(!conversionOK) return ZigbusMessage();
    ZigbusNetworkId networkId(inetid);
    int imodid = moduleName.right(4).toInt(&conversionOK);
    if(!conversionOK) return ZigbusMessage();
    ZigbusModuleId moduleId(imodid);
    ZigbusMessage message(networkId, moduleId, ZigbusOrder::CONFIG());
    message.addType(ZigbusType::NUMERIC_OUTPUT());
    message.addNumericIniState(ZigbusNumericIniState(enable));
    message.addIO(ZigbusIO(pinId));
    return message;
}

ZigbusMessage ModuleMessageHandler::create_config_meteo(const QString &moduleName, const QString &pinId, const QString& sensorType)
{
    bool conversionOK = false;
    int inetid = moduleName.left(2).toInt(&conversionOK);
    if(!conversionOK) return ZigbusMessage();
    ZigbusNetworkId networkId(inetid);
    int imodid = moduleName.right(4).toInt(&conversionOK);
    if(!conversionOK) return ZigbusMessage();
    ZigbusModuleId moduleId(imodid);
    ZigbusMessage message(networkId, moduleId, ZigbusOrder::CONFIG());
    message.addType(ZigbusType::METEO());
    message.addMeteoType(ZigbusMeteoType(sensorType));
    message.addIO(ZigbusIO(pinId));
    return message;
}

ZigbusMessage ModuleMessageHandler::create_config_pwm(const QString& moduleName, const QString& pinId, int value)
{
    bool conversionOK = false;
    int inetid = moduleName.left(2).toInt(&conversionOK);
    if(!conversionOK) return ZigbusMessage();
    ZigbusNetworkId networkId(inetid);
    int imodid = moduleName.right(4).toInt(&conversionOK);
    if(!conversionOK) return ZigbusMessage();
    ZigbusModuleId moduleId(imodid);
    ZigbusMessage message(networkId, moduleId, ZigbusOrder::CONFIG());
    message.addType(ZigbusType::PWM_OUTPUT());
    message.addPwmValue(ZigbusPwmValue(value));
    message.addIO(ZigbusIO(pinId));
    return message;
}

ZigbusMessage ModuleMessageHandler::create_config_heater(const QString& moduleName, const QString& alterp, const QString& alterm)
{
    bool conversionOK = false;
    int inetid = moduleName.left(2).toInt(&conversionOK);
    if(!conversionOK) return ZigbusMessage();
    ZigbusNetworkId networkId(inetid);
    int imodid = moduleName.right(4).toInt(&conversionOK);
    if(!conversionOK) return ZigbusMessage();
    ZigbusModuleId moduleId(imodid);
    ZigbusMessage message(networkId, moduleId, ZigbusOrder::CONFIG());
    message.addType(ZigbusType::HEATER());
    message.addIO(ZigbusIO(alterp));
    message.addIO(ZigbusIO(alterm));
    return message;
}

void ModuleMessageHandler::processMessage(QxPLMessage message)
{
    if(message.getMessageSchema() == "config") {
        if(message.getMessageClass() == "response" && message.getType() == QxPLMessage::COMMAND)
            on_xPL_config_response_cmd(message);
        else if(message.getMessageClass() == "list" && message.getType() == QxPLMessage::COMMAND
                && message.getNamedValue("command") == "request")
            on_xPL_config_list_request(message);
        else if(message.getMessageClass() == "current" && message.getType() == QxPLMessage::COMMAND
                && message.getNamedValue("command") == "request")
            on_xPL_config_list_current(message);
    }
    else if(message.getMessageSchema() == "control") {
        if(message.getMessageClass() == "basic" && message.getType() == QxPLMessage::COMMAND)
            on_xPL_basic_control_cmd(message);
    }
}

void ModuleMessageHandler::processMessage(ZigbusMessage message)
{
    if(message.getOrder() == ZigbusOrder::INIT()) {
       on_zigbus_init(message);
       return;
    }
    ZxPLModuleStat* stat = NULL;
    for(int i = 0; stat == NULL && i < modules.count(); i++) {
        if(modules.at(i)->getName() == message.fullAddress()) {
            stat = modules.at(i);
            stat->reset();
        }
    }
    if(stat == NULL)
        stat = registerModule(message.fullAddress());
    if(!stat) {
        qDebug() << "[zigbus] ERROR: failed to register module and process message \""+message.trame()+"\"";
        return;
    }
}

void ModuleMessageHandler::ping(QString moduleName)
{
    bool conversionOK = false;
    int inetid = moduleName.left(2).toInt(&conversionOK);
    if(!conversionOK) return;
    ZigbusNetworkId netid(inetid);
    int imodid = moduleName.right(4).toInt(&conversionOK);
    if(!conversionOK) return;
    ZigbusModuleId modid(imodid);
    ZigbusMessage message(netid, modid, ZigbusOrder::PING());
    emit zigbusMessageToSend(message);
}

ZxPLModuleStat* ModuleMessageHandler::registerModule(const QString& moduleName, bool hasToStart)
{
    ZxPLModuleStat *moduleStat = new ZxPLModuleStat(moduleName, this);
    connect(moduleStat, SIGNAL(iAmDead(QString)), this, SLOT(removeModule(QString)));
    connect(moduleStat, SIGNAL(iAmWarn(QString)), this, SLOT(ping(QString)));
    connect(moduleStat, SIGNAL(messageGenerated(QxPLMessage)), this, SIGNAL(xPLMessageToSend(QxPLMessage)));
    connect(moduleStat, SIGNAL(messageGenerated(ZigbusMessage)), this, SIGNAL(zigbusMessageToSend(ZigbusMessage)));
    modules.append(moduleStat);
    qDebug() << "[zigbus] Module registered: ";
    qDebug() << moduleStat->toString();
    if(hasToStart) moduleStat->start();
    return moduleStat;
}

void ModuleMessageHandler::sendHeartbeat(QString moduleName, int hbeat)
{
    QxPLMessage message(QxPLMessage::STATUS, "config", "basic");
    message.setSource(QxPLIdentifier("zigbus", "module", moduleName));
    message.setTarget(QxPLIdentifier::broadcast());
    message.setNamedValue("interval", QString::number(hbeat));
    emit xPLMessageToSend(message);
}

void ModuleMessageHandler::removeModule(QString moduleName)
{
    for(int i = 0; i < modules.count(); i++) {
        if(modules.at(i)->getName() == moduleName) {
            delete modules.takeAt(i);
            qDebug() << "[zigbus] Module " << moduleName << " is dead, and has been removed from the list";
            return;
        }
    }
    qDebug() << "[zigbus] Failed to remove stat " << moduleName;
}

void ModuleMessageHandler::on_xPL_config_response_cmd(const QxPLMessage &message)
{
    bool conversionOK = false;
    QString moduleName = message.getTarget().getInstanceId();

    int hbInterval = message.getNamedValue("interval").toInt(&conversionOK);
    if(!conversionOK) {
        qDebug() << "[zigbus] ("<<moduleName<<") config cmnd refused: interval field is missing";
        return;
    }
    QString reconf = message.getNamedValue("newconf");
    if(moduleName == "default") {
        qDebug() << "[zigbus] ("<<moduleName<<") default instance id not supported yet";
        return;
    }
    else if(moduleName != reconf) {
        qDebug() << "[zigbus] ("<<moduleName<<") Configuration cmnd refused: cannot change address...";
        return;
    }
    /*QStringList groups;
    for(int i = 0; i < message.count("group"); i++)
        groups << message.getNamedValue("group", i);
    QStringList filters;
    for(int i = 0; i < message.count("filter"); i++)
        filters << message.getNamedValue("filter", i);*/
    int device_count = message.count("device");
    QStringList config;
    for(int i = 0; i < device_count; i++) {
        QString device = message.getNamedValue("device", i);
        if(device.isEmpty()) continue;
        config.append(device);
    }
    int pingInterval = 2;
    ZModuleSettings settings(moduleName, hbInterval, pingInterval);
    settings.setDevices(config);
    if(registerConfiguration(settings))
        sendConfigurationToModule(settings);
    else
        qDebug() << "[zigbus] ("<<moduleName<<") Failed to register config. Configuration aborted";
}

void ModuleMessageHandler::on_xPL_config_list_request(const QxPLMessage &amessage)
{
    if(amessage.getTarget().getInstanceId() == "*") {
        qDebug() << "ERROR: broadcast on module names not implemented yet";
        return;
    }
    QxPLMessage message(QxPLMessage::STATUS, "config", "list");
    message.setSource(amessage.getTarget());
    message.setTarget(QxPLIdentifier::broadcast());
    message.append("config","newconf");
    message.append("option","interval");
    message.append("config","device[10]");
    emit xPLMessageToSend(message);
}

void ModuleMessageHandler::on_xPL_config_list_current(const QxPLMessage &message)
{
    ZModuleSettingsDOM dom;
    if(!dom.load("")) {
        qDebug() << dom.getLastError();
        return;
    }
    ZModuleSettings settings = dom.get(message.getTarget().getInstanceId());
    if(!settings.isValid()) {
        qDebug() << dom.getLastError();
        return;
    }
    QxPLMessage xpl_message(QxPLMessage::STATUS, "config", "current");
    xpl_message.setSource(QxPLIdentifier("zigbus-module."+settings.getName()));
    xpl_message.setTarget(QxPLIdentifier::broadcast());
    xpl_message.append("newconf", settings.getName());
    xpl_message.append("interval", QString::number(settings.getHeartbeatInterval()));
    xpl_message.append("ping", QString::number(settings.getPingInterval()));
    foreach(QString device, settings.getDevices()) {
        xpl_message.append("device", device);
    }
    emit xPLMessageToSend(xpl_message);
}

void ModuleMessageHandler::on_zigbus_init(const ZigbusMessage &message)
{

    QString moduleName = message.getNetworkId().text();
    moduleName += message.getModuleId().text();
    qDebug() << "[zigbus] init message ("<<moduleName<<")";
    // search configuration in xml file

    ZxPLModuleStat* stat = NULL;
    for(int i = 0; stat == NULL && i < modules.count(); i++) {
        if(modules.at(i)->getName() == message.fullAddress()) {
            stat = modules.at(i);
            stat->stop();
        }
    }
    if(!stat) stat = registerModule(moduleName, false);
    if(!stat) {
        qDebug() << "[zigbus] failed to register module (null pointer)";
        return;
    }
    ZModuleSettings settings = findConfiguration(moduleName);
    if(settings.isValid()) {
        sendConfigurationToModule(settings);
        stat->setHeartbeatInterval(settings.getHeartbeatInterval());
        stat->setPingInterval(settings.getPingInterval());
        stat->setAlias(settings.getAlias());
        stat->setConfigured(true);
    }
    else
        stat->setConfigured(false);
    stat->start();
}

void ModuleMessageHandler::on_xPL_basic_control_cmd(const QxPLMessage &message)
{
    if(message.getTarget().getInstanceId() == "default") return;
    bool conversionOK = false;
    QString moduleName = message.getTarget().getInstanceId();
    QString device = message.getNamedValue("device");
    QString function = xPL4Qt::function_name(device);
    QStringList args = xPL4Qt::function_arg_list(device);
    if(function.isEmpty()) {
        qDebug() << "[SKIP] unreconized function : " << device;
        return;
    }
    QString type = message.getNamedValue("type");
    QString current = message.getNamedValue("current");

    /*
      FUNC: numeric_output(<id>)
      */
    if(function == "numeric_output" && type == "output") {
        if(current != "on" && current != "off" && current != "toggle" && current != "enable" && current != "disable") {
            qDebug() << "[SKIP] bad current value "<< current << " for function " << device;
            return;
        }
        if(args.count() < 1) {
            qDebug() << "[SKIP] bad arguments for function " << device;
            return;
        }
        QString strNetwork = moduleName.left(2);
        int inetwork = strNetwork.toInt(&conversionOK);
        if(!conversionOK) {
            qDebug() << "[SKIP] Cannot parse instanceId " << message.getTarget().toString();
            return;
        }
        ZigbusNetworkId networkId(inetwork);
        QString strModule = moduleName.right(4);
        int imodule = strModule.toInt(&conversionOK, 8);
        if(!conversionOK) {
            qDebug() << "[SKIP] Cannot parse instanceId " << message.getTarget().toString();
            return;
        }
        ZigbusModuleId moduleId(imodule);
        ZigbusMessage zb_message(networkId, moduleId, ZigbusOrder(current));
        zb_message.addAddressingType(ZigbusAddressingType::ADDR_DIRECT());
        ZigbusIO io(args.at(0).trimmed());
        zb_message.addIO(io);
        QString strtime = message.getNamedValue("time");
        QString strunit = message.getNamedValue("unit");
        if(!strtime.isEmpty() && !strunit.isEmpty()) {
            int itime = strtime.toInt(&conversionOK);
            if(!conversionOK) {
                qDebug() << "[SKIP] bad time value "+strtime;
                return;
            }
            int iunit;
            if(strunit == "sec") iunit = UNIT_SEC;
            else if(strunit == "dsec") iunit = UNIT_DSEC;
            else {
                qDebug() << "[SKIP] bad time unit " << strunit;
                return;
            }
            zb_message.addTimeItem(ZigbusTimeItem(itime, iunit));
        }
        qDebug() << "translate: " << zb_message.trame();
        emit zigbusMessageToSend(zb_message);
    }
    /*
      Func:
      */
}


